﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics.Contracts;
using System.Linq;
using System.ComponentModel;

namespace tomenglertde.SetupProject.Tools
{
    /// <summary>
    /// A readonly observable collection of type TTarget where TTarget is a wrapper for type TSource.
    /// </summary>
    /// <typeparam name="TSource">The type of elements in the source collection.</typeparam>
    /// <typeparam name="TTarget">The type of elements in the wrapped collection.</typeparam>
    public class ObservableWrappedCollection<TSource, TTarget> : ReadOnlyObservableCollection<TTarget> 
    {
        private readonly Func<TSource, TTarget> _itemGenerator;

        /// <summary>
        /// Initializes a new instance of the <see cref="ObservableWrappedCollection&lt;TTarget, TSource&gt;"/> class.
        /// </summary>
        /// <param name="sourceCollection">The source collection to wrap.</param>
        /// <param name="itemGenerator">The item generator to generate the wrapper for each item.</param>
        public ObservableWrappedCollection(IEnumerable<TSource> sourceCollection, Func<TSource, TTarget> itemGenerator)
            : base(new ObservableCollection<TTarget>())
        {
            Contract.Requires(sourceCollection != null);
            Contract.Requires(itemGenerator != null);

            this._itemGenerator = itemGenerator;

            foreach (var item in sourceCollection)
            {
                Items.Add(itemGenerator(item));
            }

            var eventSource = sourceCollection as INotifyCollectionChanged;
            if (eventSource != null)
            {
                eventSource.CollectionChanged += sourceItems_CollectionChanged;
            }
        }

        public new event NotifyCollectionChangedEventHandler CollectionChanged
        {
            add
            {
                base.CollectionChanged += value;
            }
            remove
            {
                base.CollectionChanged -= value;
            }
        }

        public new event PropertyChangedEventHandler PropertyChanged
        {
            add
            {
                base.PropertyChanged += value;
            }
            remove
            {
                base.PropertyChanged -= value;
            }
        }

        [ContractVerification(false)] // Just mirrors the original collection
        private void sourceItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    var insertionIndex = e.NewStartingIndex;
                    foreach (var item in e.NewItems)
                    {
                        base.Items.Insert(insertionIndex++, _itemGenerator((TSource)item));
                    }
                    break;

                case NotifyCollectionChangedAction.Move:
                    if (e.OldItems.Count != 1)
                        throw new NotImplementedException("Moving more than one item is not supported");
                    ((ObservableCollection<TTarget>)base.Items).Move(e.OldStartingIndex, e.NewStartingIndex);
                    break;

                case NotifyCollectionChangedAction.Remove:
                    var removeIndex = e.OldStartingIndex;
                    foreach (var item in e.OldItems)
                    {
                        base.Items.RemoveAt(removeIndex);
                    }
                    break;

                case NotifyCollectionChangedAction.Replace:
                    var replaceIndex = e.OldStartingIndex;
                    foreach (var item in e.OldItems)
                    {
                        base.Items.RemoveAt(replaceIndex);
                    }
                    foreach (var item in e.NewItems)
                    {
                        base.Items.Insert(replaceIndex++, _itemGenerator((TSource)item));
                    }
                    break;

                case NotifyCollectionChangedAction.Reset:
                    base.Items.Clear();
                    foreach (TSource item in (IEnumerable)sender)
                    {
                        base.Items.Add(_itemGenerator(item));
                    }
                    break;
            }
        }

        [ContractInvariantMethod]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
        private void ObjectInvariant()
        {
            Contract.Invariant(this._itemGenerator != null);
        }
    }
}